unit MainGui;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, StdCtrls, ExtCtrls,

  InventionCalc, Invention, Items, Shopping, Location, Region,
  FilterGui, ColumnsGui, ShowInfoGui, ItemsGui, OrganizeGui, HelpGui, UserSettingsGui, ShoppingGui, ResultsGui, AddResultsGui, ReactionGui,
  xmldom, XMLIntf, msxmldom, XMLDoc, oxmldom,
  GlobalRecordsAndEnums, Menus, Buttons;

type
  SFilter = record
    Kind: Integer; // 0 = category, 1 = subcategory, 2 = group;
    ID: Integer;
  end;

  TfrmMainGui = class(TForm)
    MainMenu: TMainMenu;
    mnFile: TMenuItem;
    mnFileExit: TMenuItem;
    mnAdminDecryptors: TMenuItem;
    mnFileCategorize: TMenuItem;
    N1: TMenuItem;
    ListView: TListView;
    ListPanel: TPanel;
    btnColumns: TButton;
    btnFilter: TButton;
    Panel: TPanel;
    gbT1Item: TGroupBox;
    lblName: TLabel;
    lblInventionRace: TLabel;
    lblT2Items: TLabel;
    lblCopyTime: TLabel;
    lblProductionLimit: TLabel;
    lblMaxRunCopyTime: TLabel;
    lblItemName: TLabel;
    lblItemRace: TLabel;
    lblItemCopyTime: TLabel;
    lblItemProductionLimit: TLabel;
    lblItemMaxRunCopyTime: TLabel;
    gbInventionInput: TGroupBox;
    gbT2Item1InventionOutput: TGroupBox;
    lblDecryptor: TLabel;
    cbDecryptors: TComboBox;
    lblRequiredMaterialsCost: TLabel;
    lblDecryptorCost: TLabel;
    lblMetaItem: TLabel;
    edtMetaItem: TEdit;
    lblMetaItemCost: TLabel;
    lblBPCRuns: TLabel;
    lblInventionCost: TLabel;
    cbMetaItem: TComboBox;
    lblCostPerJob: TLabel;
    lblOutputRuns: TLabel;
    lblItemOutputRuns: TLabel;
    lblT2item: TLabel;
    lblPrice: TLabel;
    edtItemCost: TEdit;
    lblItemCost: TLabel;
    lblBuildCost: TLabel;
    lblItemME: TLabel;
    lblItemBuildCost: TLabel;
    lblT2ItemRevenue: TLabel;
    lblItemInventionRevenue: TLabel;
    lblInventionType: TLabel;
    lblItemInventionType: TLabel;
    Panel1: TPanel;
    mnHelp: TMenuItem;
    mnHelpAbout: TMenuItem;
    mnAdminExport: TMenuItem;
    mnEdit: TMenuItem;
    mnEditItems: TMenuItem;
    mnAdmin: TMenuItem;
    mnAdminGenerateT1T2: TMenuItem;
    mnAdminImport: TMenuItem;
    N4: TMenuItem;
    mnAdminListMarketgroups: TMenuItem;
    mnAdminListItemsMarketGroup: TMenuItem;
    N5: TMenuItem;
    mnAdminCreateCategoryList: TMenuItem;
    lstT2Items: TListView;
    lblRequiredMaterials: TLabel;
    tbCopy: TTrackBar;
    lblItemBPCRuns: TLabel;
    lblCopyInstallation: TLabel;
    lblInventionInstallation: TLabel;
    lblInventionInstallationCost: TLabel;
    lblCopyInstallationCost: TLabel;
    gbInventionProfit: TGroupBox;
    lblAvgChance: TLabel;
    lblAvgAttempts: TLabel;
    lblAvgCost: TLabel;
    lblAvgRevenue: TLabel;
    lblAvgProfitperjob: TLabel;
    lblSuccess: TLabel;
    lblAttempts: TLabel;
    lblSuccessCost: TLabel;
    lblRevenue: TLabel;
    lblProfitperjob: TLabel;
    lblCopyInstallationTime: TLabel;
    lblInventionInstallationTime: TLabel;
    btnCopy: TButton;
    lblAvgProfitPerSuccess: TLabel;
    lblProfitpersuccess: TLabel;
    lblT2ItemName: TLabel;
    mnEditSettings: TMenuItem;
    imgShowInfo1: TImage;
    imgShowInfo2: TImage;
    lblManufactureInstallation: TLabel;
    lblManufactureInstallationTime: TLabel;
    lblManufactureInstallationCost: TLabel;
    mnExtraShopping: TMenuItem;
    mnExtraShoppingList: TMenuItem;
    mnExtra: TMenuItem;
    N3: TMenuItem;
    mnExtraShoppingAddT2: TMenuItem;
    mnExtraShoppingAddInvention: TMenuItem;
    Inventionresults1: TMenuItem;
    mnExtraResultsList: TMenuItem;
    N6: TMenuItem;
    mnResultsAdd: TMenuItem;
    mnAdminImportInstallations: TMenuItem;
    N2: TMenuItem;
    mnHelpHelp: TMenuItem;
    lblTotalCost: TLabel;
    lblTotalInventionCost: TLabel;
    mnAdminImportRegions: TMenuItem;
    FilterPanel: TPanel;
    lblFilter: TLabel;
    cbFilter: TComboBox;
    lblRevenuePerDay: TLabel;
    lblItemInventionRevenuePerDay: TLabel;
    N7: TMenuItem;
    AnatolixExportText1: TMenuItem;

    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnFilterClick(Sender: TObject);
    procedure mnAdminDecryptorsClick(Sender: TObject);
    procedure mnFileCategorizeClick(Sender: TObject);
    procedure mnFileExitClick(Sender: TObject);
    procedure btnColumnsClick(Sender: TObject);
    procedure ListViewColumnClick(Sender: TObject; Column: TListColumn);
    procedure ListViewCompare(Sender: TObject; Item1, Item2: TListItem;
      Data: Integer; var Compare: Integer);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure cbDecryptorsChange(Sender: TObject);
    procedure edtMetaItemChange(Sender: TObject);
    procedure edtItemCostChange(Sender: TObject);
    procedure cbMetaItemChange(Sender: TObject);
    procedure lblBuildCostClick(Sender: TObject);
    procedure mnHelpAboutClick(Sender: TObject);
    procedure mnAdminExportClick(Sender: TObject);
    procedure ListViewChange(Sender: TObject; Item: TListItem;
      Change: TItemChange);
    procedure mnEditItemsClick(Sender: TObject);
    procedure mnAdminGenerateT1T2Click(Sender: TObject);
    procedure mnAdminImportClick(Sender: TObject);
    procedure mnAdminListMarketgroupsClick(Sender: TObject);
    procedure mnAdminListItemsMarketGroupClick(Sender: TObject);
    procedure mnAdminCreateCategoryListClick(Sender: TObject);
    procedure imgShowInfo1Click(Sender: TObject);
    procedure lstT2ItemsChange(Sender: TObject; Item: TListItem;
      Change: TItemChange);
    procedure tbCopyChange(Sender: TObject);
    procedure imgShowInfo2Click(Sender: TObject);
    procedure btnCopyClick(Sender: TObject);
    procedure mnEditSettingsClick(Sender: TObject);
    procedure lblRequiredMaterialsClick(Sender: TObject);
    procedure mnExtraShoppingListClick(Sender: TObject);
    procedure mnExtraShoppingAddInventionClick(Sender: TObject);
    procedure mnExtraShoppingAddT2Click(Sender: TObject);
    procedure mnExtraResultsListClick(Sender: TObject);
    procedure mnResultsAddClick(Sender: TObject);
    procedure mnAdminImportInstallationsClick(Sender: TObject);
    procedure mnHelpHelpClick(Sender: TObject);
    procedure mnAdminImportRegionsClick(Sender: TObject);
    procedure ListViewSelectItem(Sender: TObject; Item: TListItem;
      Selected: Boolean);
    procedure AnatolixExportText1Click(Sender: TObject);
  private
    loading: Boolean;
//    sortcolumnindex: Integer;

    inventionCalc: TInventionCalc;

    organizegui: TfrmOrganize;
    shoppinggui: TfrmShopping;
    editnewitemsgui: TfrmEditNewItems;
    usersettingsgui: TfrmUserSettings;
    resultsgui: TfrmResults;
    addresultsgui: TfrmAddResult;

    activeinvention: TInvention;
    isClosing: Boolean;

    procedure DrawColumns;
    procedure LoadItems;
    procedure LoadInvention;
    procedure ClearInvention;
    procedure LoadFilter;

    procedure UpdateInvention(update_edtItemCost: Boolean = true);

    procedure frmOrganizeClose(Sender: TObject; var Action: TCloseAction);
    procedure frmShoppingClose(Sender: TObject; var Action: TCloseAction);
    procedure frmEditNewItemsClose(Sender: TObject; var Action: TCloseAction);
    procedure frmUserSettingsClose(Sender: TObject; var Action: TCloseAction);
    procedure frmResultsClose(Sender: TObject; var Action: TCloseAction);
    procedure frmAddResultsClose(Sender: TObject; var Action: TCloseAction);
  public
  end;

var
  frmMainGui: TfrmMainGui;
  categories, subcategories, groups: TList; // filtered categories, subcategories and groups. Items that belong to all of the objects here can be shown

implementation

{$R *.dfm}

uses
  Category, Decryptor, Math, StrUtils, ExtActns;

procedure TfrmMainGui.FormCreate(Sender: TObject);
begin
  isClosing := false;
  inventionCalc := TInventionCalc.Create;

  if (adminhax) then
    mnAdmin.Visible := true;

  categories := TList.Create;
  subcategories := TList.Create;
  groups := TList.Create;

  LoadFilter;

  DrawColumns;

  LoadItems;

  frmMainGui.Width := inventionCalc.UserSettings.Settings.AppWidth;
  frmMainGui.Height := inventionCalc.UserSettings.Settings.AppHeight;
  frmMainGui.Left := inventionCalc.UserSettings.Settings.AppLeft;
  frmMainGui.Top := inventionCalc.UserSettings.Settings.AppTop;
end;

//===============================
//====== Loading functions ======
//===============================

procedure TfrmMainGui.LoadFilter;
var
  stream: TMemoryStream;
  filter: SFilter;
  category: TCategory;
  subcategory: TSubcategory;
  group: TGroup;
begin
  if (FileExists('res\Filter.dat')) then begin
    categories.Clear;
    subcategories.Clear;
    groups.Clear;

    stream := TMemoryStream.Create;
    stream.Position := 0;
    stream.LoadFromFile('res\Filter.dat');
    while stream.Position < stream.Size do begin
      try
        stream.Read(filter, sizeof(SFilter));
      except
       on e: Exception do
        MessageDlg('Error loading filter:'+e.Message, mtError, [mbOk], 0);
      end;
      case filter.Kind of
        0: begin
             category := inventionCalc.GetCategoryByID(filter.ID);
             if (category <> nil) then categories.Add(category);
           end;
        1: begin
             subcategory := inventionCalc.GetSubcategoryByID(filter.ID);
             if (subcategory <> nil) then subcategories.Add(subcategory);
           end;
        2: begin
             group := inventionCalc.GetGroupByID(filter.ID);
             if (group <> nil) then groups.Add(group);
           end;
      end;
    end;
    stream.Free;
  end;
end;

procedure TfrmMainGui.DrawColumns;
var
  i, totalwidth: Integer;
  col: TListColumn;
  columns: Array [0..10] of EColumn;
begin
  ListView.Items.Clear;
  ListView.Columns.Clear;
  for i := 0 to 10 do begin        // read columns
    columns[i] := inventionCalc.UserSettings.Settings.Columns[i];
  end;

  // create columns based on the compressed array
  totalwidth := 0;
  for i := 0 to 10 do begin
    if (columns[i] <> COL_None) then begin
      case columns[i] of
        COL_ID: begin
             col := ListView.Columns.Add;
             col.Caption := 'ID';
             col.Width := 45;
             totalwidth := totalwidth + 45;
           end;
        COL_Name: begin
             col := ListView.Columns.Add;
             col.Caption := 'Name';
             col.Width := 195;
             totalwidth := totalwidth + 195;
           end;
        COL_InventionType: begin
             col := ListView.Columns.Add;
             col.Caption := 'Invention Type';
             col.Width := 60;
             totalwidth := totalwidth + 60;
           end;
        COL_Datacore1: begin
             col := ListView.Columns.Add;
             col.Caption := 'Datacore 1';
             col.Width := 155;
             totalwidth := totalwidth + 155;
           end;
        COL_Datacore2: begin
             col := ListView.Columns.Add;
             col.Caption := 'Datacore 2';
             col.Width := 155;
             totalwidth := totalwidth + 155;
           end;
        COL_T2Item1: begin
             col := ListView.Columns.Add;
             col.Caption := 'T2 Item 1';
             col.Width := 195;
             totalwidth := totalwidth + 195;
           end;
        COL_T2Item2: begin
             col := ListView.Columns.Add;
             col.Caption := 'T2 Item 2';
             col.Width := 195;
             totalwidth := totalwidth + 195;
           end;
        COL_InventionProfit: begin
             col := ListView.Columns.Add;
             col.Caption := 'Invention Profit';
             col.Width := 100;
             totalwidth := totalwidth + 100;
           end;
        COL_BuildCost: begin
             col := ListView.Columns.Add;
             col.Caption := 'Build Cost';
             col.Width := 100;
             totalwidth := totalwidth + 100;
           end;
        COL_Race: begin
             col := ListView.Columns.Add;
             col.Caption := 'Race';
             col.Width := 120;
             totalwidth := totalwidth + 120;
           end;
        COL_T1ItemProfit: begin
             col := ListView.Columns.Add;
             col.Caption := 'Build Profit';
             col.Width := 100;
             totalwidth := totalwidth + 100;
           end;
      end;
    end;
  end;
  // Min(totalwidth + 5, 600)   <= width of the sum of all columns
  // 407                        <= width of the rest of the form
  // 16                         <= width of the scrollbar
  // 9                          <= Vista skin...

  frmMainGui.Width := Min(totalwidth + 5, 600) + 407 + 16 + 9;
{  if (inventionCalc.UserSettings.Settings.SortColumn = COL_None) then begin
    sortcolumnindex := -1;
  end
  else begin
    for i := 0 to 9 do begin
      if (columns[i] = inventionCalc.UserSettings.Settings.SortColumn) then sortcolumnindex := i;
    end;
  end;}
end;

procedure TfrmMainGui.LoadItems;
// fills the listview with items, based on the columns and filter
var
  i, j: Integer;
  t1: TNewItem;
  item: TListItem;
  allowedtoadd: Boolean;
  inv: TInvention;
  me: Integer;
begin
  ListView.Items.Clear;
  inv := nil;
  for i := 0 to inventionCalc.GetT1ItemCount -1 do begin
    t1 := inventionCalc.GetT1ItemByIndex(i);
    if (t1.Category <> nil) then begin
      allowedtoadd := categories.IndexOf(t1.Category) >= 0;
      if (t1.Subcategory <> nil) then allowedtoadd := ((allowedtoadd) and (subcategories.IndexOf(t1.Subcategory) >= 0));
      if (t1.Group <> nil) then allowedtoadd := ((allowedtoadd) and (groups.IndexOf(t1.Group) >= 0));
      if (t1.InventionRace = Amarr) then allowedtoadd := (allowedtoadd and inventionCalc.UserSettings.Settings.FilterAmarr)
      else if (t1.InventionRace = Caldari) then allowedtoadd := (allowedtoadd and inventionCalc.UserSettings.Settings.FilterCaldari)
      else if (t1.InventionRace = Gallente) then allowedtoadd := (allowedtoadd and inventionCalc.UserSettings.Settings.FilterGallente)
      else if (t1.InventionRace = Minmatar) then allowedtoadd := (allowedtoadd and inventionCalc.UserSettings.Settings.FilterMinmatar);
      if (allowedtoadd) then begin
        item := ListView.Items.Add;
        item.Data := t1;
        case inventionCalc.UserSettings.Settings.Columns[0] of
          COL_ID: item.Caption := IntToStr(t1.ID);
          COL_Name: item.Caption := t1.Name;
          COL_InventionType: item.Caption := EInventionTypeToStringShort(t1.InventionType);
          COL_Datacore1: item.Caption := ESkillToString(t1.ScienceSkill1);
          COL_Datacore2: item.Caption := ESkillToString(t1.ScienceSkill2);
          COL_T2Item1: begin
               if (t1.GetUsedIn(0) <> nil) then item.Caption := t1.GetUsedIn(0).Item.Name
               else item.Caption := '';
             end;
          COL_T2Item2: begin
               if (t1.GetUsedIn(1) <> nil) then item.Caption := t1.GetUsedIn(1).Item.Name
               else item.Caption := '';
             end;
          COL_InventionProfit: begin
               if (inv <> nil) then inv.Destroy;
               inv := TInvention.Create(t1, inventionCalc);
               item.Caption := FloatToStrF(inv.GetBestProfitPerJobWithDecryptor, ffNumber, 11, 2);
               inv.Destroy; inv := nil;
             end;
          COL_BuildCost: item.Caption := FloatToStrF(t1.GetBuildCost(1, me, inventionCalc.UserSettings.Installations.ManufactureMaterialModifier * inventionCalc.UserSettings.Skills.GetProductionEfficiencyModifier), ffNumber, 11, 2);
          COL_Race: item.Caption := ERaceToString(t1.InventionRace);
          COL_T1ItemProfit: item.Caption := FloatToStrF(t1.Price - t1.GetBuildCost(1, t1.BPOME, inventionCalc.UserSettings.Installations.ManufactureMaterialModifier * inventionCalc.UserSettings.Skills.GetProductionEfficiencyModifier), ffNumber, 11, 2);
          end;
        for j := 1 to 9 do begin
          case inventionCalc.UserSettings.Settings.Columns[j] of
            COL_ID: item.SubItems.Add(IntToStr(t1.ID));
            COL_Name: item.SubItems.Add(t1.Name);
            COL_InventionType: item.SubItems.Add(EInventionTypeToStringShort(t1.InventionType));
            COL_Datacore1: item.SubItems.Add(ESkillToString(t1.ScienceSkill1));
            COL_Datacore2: item.SubItems.Add(ESkillToString(t1.ScienceSkill2));
            COL_T2Item1: begin
                 if (t1.GetUsedIn(0) <> nil) then item.SubItems.Add(t1.GetUsedIn(0).Item.Name)
                 else item.SubItems.Add('');
               end;
            COL_T2Item2: begin
                 if (t1.GetUsedIn(1) <> nil) then item.SubItems.Add(t1.GetUsedIn(1).Item.Name)
                 else item.SubItems.Add('');
               end;
            COL_InventionProfit: begin
                 if (inv <> nil) then inv.Destroy;
                 inv := TInvention.Create(t1, inventionCalc);
                 item.SubItems.Add(FloatToStrF(inv.GetBestProfitPerJobWithDecryptor, ffNumber, 11, 2));
                 inv.Destroy; inv := nil;
               end;
            COL_BuildCost: item.SubItems.Add(FloatToStrF(t1.GetBuildCost(1, me, inventionCalc.UserSettings.Installations.ManufactureMaterialModifier * inventionCalc.UserSettings.Skills.GetProductionEfficiencyModifier), ffNumber, 11, 2));
            COL_Race: item.SubItems.Add(ERaceToString(t1.InventionRace));
            COL_T1ItemProfit: item.SubItems.Add(FloatToStrF(t1.Price - t1.GetBuildCost(1, t1.BPOME, inventionCalc.UserSettings.Installations.ManufactureMaterialModifier * inventionCalc.UserSettings.Skills.GetProductionEfficiencyModifier), ffNumber, 11, 2));
          end;
        end;
      end;
    end
//    else unassigned item
  end;
  ListView.AlphaSort;
end;

procedure TfrmMainGui.LoadInvention;
// creates a TInvention object, which will be the active invention. From this object, the procedure UpdateInvention can
// retrieve all information that is needed to fill the right part of the main form. UpdateInvention however only updates
// dynamic information. Static information, such as the item name, invention type, etc., is filled by this procedure.
var
  t1: TNewItem;
  item: TNewItem;
  listitem: TListItem;
  i: Integer;
  decryptor: TDecryptor;
begin
  if (ListView.Selected <> nil) then begin
    loading := true;
    Panel.Enabled := true;

    // load static information
    t1 := TNewItem(ListView.Selected.Data);
    if (activeinvention <> nil) then activeinvention.Destroy;
    activeinvention := TInvention.Create(t1, inventionCalc);

    lblItemName.Caption := t1.Name;
    lblItemRace.Caption := ERaceToString(t1.InventionRace);
    lblItemInventionType.Caption := EInventionTypeToString(t1.InventionType);
    lstT2Items.Clear;
    for i := 0 to t1.UsedInCount -1 do begin
      item := t1.GetUsedIn(i).Item;
      listitem := lstT2Items.Items.Add;
      listitem.Caption := item.Name;
      listitem.Data := item;
    end;

    lblItemCopyTime.Caption := SecondsToString(activeinvention.GetSingleRunCopyTime);
//    lblItemCopyTime.Caption := SecondsToString(t1.CopyTime);
    lblItemProductionLimit.Caption := IntToStr(t1.ProductionLimit);
    lblItemMaxRunCopyTime.Caption := SecondsToString(activeinvention.GetMaxRunCopyTime);
    if (activeinvention.GetMaxRunCopyTime >= 604800) then lblItemMaxRunCopyTime.Font.Color := clRed  // 604800 being one week
    else lblItemMaxRunCopyTime.Font.Color := clWindowText;

    cbDecryptors.Items.Clear;
    cbDecryptors.Items.AddObject('None', nil);
    cbDecryptors.ItemIndex := 0;
    case t1.InventionRace of
      Amarr: begin
        for i := 0 to inventionCalc.AmarrDecryptorCount -1 do begin
          decryptor := inventionCalc.GetDecryptor(Amarr, i);
          cbDecryptors.Items.AddObject(decryptor.Name, decryptor);
          if (t1.InventionType = Normal) then begin
            if (decryptor.ID = inventionCalc.UserSettings.Default.NormalAmarrDecryptorID) then cbDecryptors.ItemIndex := i+1;
          end
          else
            if (decryptor.ID = inventionCalc.UserSettings.Default.ShipAmarrDecryptorID) then cbDecryptors.ItemIndex := i+1
        end;
      end;
      Caldari: begin
        for i := 0 to inventionCalc.CaldariDecryptorCount -1 do begin
          decryptor := inventionCalc.GetDecryptor(Caldari, i);
          cbDecryptors.Items.AddObject(decryptor.Name, decryptor);
          if (t1.InventionType = Normal) then begin
            if (decryptor.ID = inventionCalc.UserSettings.Default.NormalCaldariDecryptorID) then cbDecryptors.ItemIndex := i+1;
          end
          else
            if (decryptor.ID = inventionCalc.UserSettings.Default.ShipCaldariDecryptorID) then cbDecryptors.ItemIndex := i+1
        end;
      end;
      Gallente: begin
        for i := 0 to inventionCalc.GallenteDecryptorCount -1 do begin
          decryptor := inventionCalc.GetDecryptor(Gallente, i);
          cbDecryptors.Items.AddObject(decryptor.Name, decryptor);
          if (t1.InventionType = Normal) then begin
            if (decryptor.ID = inventionCalc.UserSettings.Default.NormalGallenteDecryptorID) then cbDecryptors.ItemIndex := i+1;
          end
          else
            if (decryptor.ID = inventionCalc.UserSettings.Default.ShipGallenteDecryptorID) then cbDecryptors.ItemIndex := i+1
        end;
      end;
      Minmatar: begin
        for i := 0 to inventionCalc.MinmatarDecryptorCount -1 do begin
          decryptor := inventionCalc.GetDecryptor(Minmatar, i);
          cbDecryptors.Items.AddObject(decryptor.Name, decryptor);
          if (t1.InventionType = Normal) then begin
            if (decryptor.ID = inventionCalc.UserSettings.Default.NormalMinmatarDecryptorID) then cbDecryptors.ItemIndex := i+1;
          end
          else
            if (decryptor.ID = inventionCalc.UserSettings.Default.ShipMinmatarDecryptorID) then cbDecryptors.ItemIndex := i+1
        end;
      end;
    end;

    cbMetaitem.Clear;
    cbMetaItem.Items.Add('0');
    cbMetaItem.Items.Add('1');
    cbMetaItem.Items.Add('2');
    cbMetaItem.Items.Add('3');
    cbMetaItem.Items.Add('4');
    cbMetaItem.ItemIndex := activeinvention.MetaItemLevel;
    if (activeinvention.MetaItemCost > 0) then
      edtMetaItem.Text := FloatToStr(activeinvention.MetaItemCost)
    else
      edtMetaItem.Text := '<meta-item price>';

    tbCopy.Min := 1;
    tbCopy.Max := t1.ProductionLimit;

    UpdateInvention;
    loading := false;
  end;
end;

procedure TfrmMainGui.ClearInvention;
begin
  loading := true;
  // static information
  lblItemName.Caption := '';
  lblItemRace.Caption := '';
  lblItemInventionType.Caption := '';
  lstT2Items.Clear;

  lblItemCopyTime.Caption := '';
  lblItemProductionLimit.Caption := '';
  lblItemMaxRunCopyTime.Caption := '';

  cbDecryptors.Items.Clear;

  cbMetaitem.Clear;
  cbMetaItem.Items.Add('<lvl>');
  cbMetaItem.ItemIndex := 0;
  edtMetaItem.Text := '<meta-item price>';

  tbCopy.Min := 1;
  tbCopy.Max := 10;

  // dynamic information
  lblRequiredMaterialsCost.Caption := '';
  lblDecryptorCost.Caption := '';
  lblMetaItemCost.Caption := '';
  tbCopy.Position := 0;
  lblItemBPCRuns.Caption := '';
  lblCopyInstallationTime.Caption := '';

  lblCopyInstallationCost.Caption := '';
  lblInventionInstallationTime.Caption := '';
  lblInventionInstallationCost.Caption := '';

  lblInventionCost.Caption := '';

  lblT2ItemName.Caption := '';
  edtItemCost.Text := '';
  lblItemCost.Caption := '';
  lblItemME.Caption := '';
  lblItemBuildCost.Caption := '';
  lblItemOutputRuns.Caption := '';
  lblManufactureInstallationTime.Caption := '';
  lblManufactureInstallationCost.Caption := '';

  lblT2ItemRevenue.Caption := 'Revenue for this item:';
  lblItemInventionRevenue.Caption := '';
  lblItemInventionRevenuePerDay.Caption := '';
  lblSuccess.Caption := '';
  lblAttempts.Caption := '';
  lblSuccessCost.Caption := '';
  lblRevenue.Caption := '';
  lblProfitPersuccess.Caption := '';
  lblProfitperjob.Caption := '';

  lblTotalInventionCost.Caption := '';

  Panel.Enabled := false;
  loading := false;
end;

procedure TfrmMainGui.UpdateInvention(update_edtItemCost: Boolean = true);
// Updates the active invention if a parameter has changed (decryptor selection for example). Only updates dynamic
// information, such as revenue and profit per job. Static information, such as item name, race, type, is loaded by
// LoadInvention
var
  revenue, profitperjob: double;
begin
  // Invention input
  lblRequiredMaterialsCost.Caption := FloatToStrF(activeinvention.GetRequiredMaterialsCost, ffNumber, 11, 2);
  lblDecryptorCost.Caption := FloatToStrF(activeinvention.GetDecryptorCost, ffNumber, 11, 2);
  lblMetaItemCost.Caption := FloatToStrF(activeinvention.MetaItemCost, ffNumber, 11, 2);
  tbCopy.Position := activeinvention.BPCRuns;
  lblItemBPCRuns.Caption := IntToStr(activeinvention.BPCRuns);
  lblCopyInstallationTime.Caption := CutString(SecondsToString(activeinvention.GetTotalCopyTime), lblCopyInstallationTime);
  if (activeinvention.GetTotalCopyTime >= 604800) then lblCopyInstallationTime.Font.Color := clRed
  else lblCopyInstallationTime.Font.Color := clWindowText;

  lblCopyInstallationCost.Caption := FloatToStrF(activeinvention.GetInstallationCost(ACT_Copying), ffNumber, 11, 2);
  lblInventionInstallationTime.Caption := CutString(SecondsToString(activeinvention.GetTotalInventionTime), lblInventionInstallationTime);
  if (activeinvention.GetTotalInventionTime >= 604800) then lblInventionInstallationTime.Font.Color := clRed
  else lblInventionInstallationTime.Font.Color := clWindowText;
  lblInventionInstallationCost.Caption := FloatToStrF(activeinvention.GetInstallationCost(ACT_Invention), ffNumber, 11, 2);

  lblInventionCost.Caption := FloatToStrF(activeinvention.GetInventionJobCost, ffNumber, 11, 2);

  // Invention output
  if (activeinvention.T2Item <> nil) then begin
    lblT2ItemName.Caption := activeinvention.T2Item.Name;
    if (update_edtItemCost) then edtItemCost.Text := FloatToStr(activeinvention.T2Item.Price);
    lblItemCost.Caption := FloatToStrF(activeinvention.T2Item.Price, ffNumber, 11, 2);
  end
  else lblItemCost.Caption := '0';
  lblItemME.Caption := 'at ME ' + IntToStr(activeinvention.GetOutputME);
  lblItemBuildCost.Caption := FloatToStrF(activeinvention.GetT2ItemBuildCost, ffNumber, 11, 2);
  lblItemOutputRuns.Caption := IntToStr(activeinvention.GetOutputRuns);
  lblManufactureInstallationTime.Caption := SecondsToString(activeinvention.GetT2ManufactureTime) + ' (' + SecondsToString(activeinvention.GetTotalT2ManufactureTime) + ')';
  lblManufactureInstallationCost.Caption := FloatToStrF(activeinvention.GetInstallationCost(ACT_Manufacture), ffNumber, 11, 2);

  if (inventionCalc.UserSettings.Settings.UseFees) then lblT2ItemRevenue.Caption := 'Revenue for this item, incl. sales tax && brokers fee:'
  else lblT2ItemRevenue.Caption := 'Revenue for this item, excl. sales tax && brokers fee:';
  revenue := activeinvention.GetT2ItemInventionRevenue;

  lblItemInventionRevenue.Caption := FloatToStrF(revenue, ffNumber, 11, 2);
  if (revenue > 0) then lblItemInventionRevenue.Font.Color := clGreen
  else lblItemInventionRevenue.Font.Color := clRed;

  lblItemInventionRevenuePerDay.Caption := FloatToStrF(activeinvention.GetProfitPermanufactureDay, ffNumber, 11, 2);
  if (revenue > 0) then lblItemInventionRevenuePerDay.Font.Color := clGreen
  else lblItemInventionRevenuePerDay.Font.Color := clRed;


  // Invention profit
  lblSuccess.Caption := FloatToStrF(activeinvention.GetSuccess * 100, ffNumber, 11, 2) + ' %';
  lblAttempts.Caption := FloatToStrF(activeinvention.GetAttempts, ffNumber, 11, 2);
  lblSuccessCost.Caption := FloatToStrF(activeinvention.GetSuccessCost, ffNumber, 11, 2);

  revenue := activeinvention.GetT2ItemInventionRevenue;
  lblRevenue.Caption := FloatToStrF(revenue, ffNumber, 11, 2);
  if (revenue > 0) then lblRevenue.Font.Color := clGreen
  else lblRevenue.Font.Color := clRed;

  revenue := activeinvention.GetProfitPerSuccess;
  lblProfitPersuccess.Caption := FloatToStrF(revenue, ffNumber, 11, 2);
  if (revenue > 0) then lblProfitPersuccess.Font.Color := clGreen
  else lblProfitPersuccess.Font.Color := clRed;

  profitperjob := activeinvention.GetProfitPerJob;
  lblProfitperjob.Caption := FloatToStrF(profitperjob, ffNumber, 11, 2);
  if (profitperjob > 0) then lblProfitperjob.Font.Color := clGreen
  else lblProfitperjob.Font.Color := clRed;

  lblTotalInventionCost.Caption := FloatToStrF(activeinvention.GetTotalInventionCost, ffNumber, 11, 2);
end;

//=========================================
//====== Main form exiting functions ======
//=========================================

procedure TfrmMainGui.FormDestroy(Sender: TObject);
begin
  if (organizegui <> nil) then organizegui.Close;
  if (shoppinggui <> nil) then shoppinggui.Close;
  if (editnewitemsgui <> nil) then editnewitemsgui.Close;
  if (usersettingsgui <> nil) then usersettingsgui.Close;
  if (resultsgui <> nil) then resultsgui.Close;
  if (addresultsgui <> nil) then addresultsgui.Close;

  inventionCalc.Destroy;
end;

procedure TfrmMainGui.mnFileExitClick(Sender: TObject);
begin
  frmMainGui.Close;
  Application.Terminate;
end;

procedure TfrmMainGui.FormClose(Sender: TObject; var Action: TCloseAction);
var
  i: integer;
  filter: SFilter;
  stream: TMemoryStream;
begin
  isClosing := true;
  inventionCalc.UserSettings.Settings.AppWidth := frmMainGui.Width;
  inventionCalc.UserSettings.Settings.AppHeight := frmMainGui.Height;
  inventionCalc.UserSettings.Settings.AppTop := frmMainGui.Top;
  inventionCalc.UserSettings.Settings.AppLeft := frmMainGui.Left;

  stream := TMemoryStream.Create;
  for i := 0 to categories.Count -1 do begin
    filter.Kind := 0;
    filter.ID := TCategory(categories.Items[i]).ID;
    stream.Write(filter, SizeOf(SFilter));
  end;
  for i := 0 to subcategories.Count -1 do begin
    filter.Kind := 1;
    filter.ID := TSubcategory(subcategories.Items[i]).ID;
    stream.Write(filter, SizeOf(SFilter));
  end;
  for i := 0 to groups.Count -1 do begin
    filter.Kind := 2;
    filter.ID := TGroup(groups.Items[i]).ID;
    stream.Write(filter, SizeOf(SFilter));
  end;
  try
    stream.SaveToFile('res\Filter.dat');
  except
    on e: Exception do MessageDlg(e.Message, mtError, [mbOk], 0);
  end;
  stream.Free;
  categories.Destroy;
  subcategories.Destroy;
  groups.Destroy;
end;

//===================================================
//====== ListView (and related) event handlers ======
//===================================================

procedure TfrmMainGui.btnFilterClick(Sender: TObject);
var
  filter: TfrmFilter;
begin
  filter := TfrmFilter.Create(self, inventionCalc, categories, subcategories, groups);
  filter.ShowModal;
  if (filter.ModalResult = mrOK) then begin
    LoadItems;
  end;
  filter.Destroy;
end;

procedure TfrmMainGui.btnColumnsClick(Sender: TObject);
var
  gui: TfrmColumns;
begin
  gui := TfrmColumns.Create(self, inventionCalc);
  gui.ShowModal;
  if (gui.ModalResult = mrOK) then begin
    DrawColumns;
    LoadItems;
  end;
  gui.Destroy;
end;

procedure TfrmMainGui.ListViewColumnClick(Sender: TObject;
  Column: TListColumn);
begin
  if (inventionCalc.UserSettings.Settings.Columns[Column.Index] = inventionCalc.UserSettings.Settings.SortColumn) then
    // if the column that was clicked is the column that is the primary column for sorting, only reverse its direction
    inventionCalc.UserSettings.Settings.SortDirection := (not inventionCalc.UserSettings.Settings.SortDirection)
  else begin
    // otherwise, add the column that was clicked as the primary sort column and demote the old primary column to
    // secondary column (and demote the old secondary column to tertiary column)
//    sortcolumnindex := Column.Index;
    inventionCalc.UserSettings.Settings.SortColumn3 := inventionCalc.UserSettings.Settings.SortColumn2;
    inventionCalc.UserSettings.Settings.SortDirection3 := inventionCalc.UserSettings.Settings.SortDirection2;
    inventionCalc.UserSettings.Settings.SortColumn2 := inventionCalc.UserSettings.Settings.SortColumn;
    inventionCalc.UserSettings.Settings.SortDirection2 := inventionCalc.UserSettings.Settings.SortDirection;
    if (Column.Caption = 'ID') then inventionCalc.UserSettings.Settings.SortColumn := COL_ID
    else if (Column.Caption = 'Name') then inventionCalc.UserSettings.Settings.SortColumn := COL_Name
    else if (Column.Caption = 'Invention Type') then inventionCalc.UserSettings.Settings.SortColumn := COL_InventionType
    else if (Column.Caption = 'Datacore 1') then inventionCalc.UserSettings.Settings.SortColumn := COL_Datacore1
    else if (Column.Caption = 'Datacore 2') then inventionCalc.UserSettings.Settings.SortColumn := COL_Datacore2
    else if (Column.Caption = 'T2 Item 1') then inventionCalc.UserSettings.Settings.SortColumn := COL_T2Item1
    else if (Column.Caption = 'T2 Item 2') then inventionCalc.UserSettings.Settings.SortColumn := COL_T2Item2
    else if (Column.Caption = 'Invention Profit') then inventionCalc.UserSettings.Settings.SortColumn := COL_InventionProfit
    else if (Column.Caption = 'Build Cost') then inventionCalc.UserSettings.Settings.SortColumn := COL_BuildCost
    else if (Column.Caption = 'Race') then inventionCalc.UserSettings.Settings.SortColumn := COL_Race
    else if (Column.Caption = 'Build Profit') then inventionCalc.UserSettings.Settings.SortColumn := COL_T1ItemProfit;
    inventionCalc.UserSettings.Settings.SortDirection := true;
  end;
  TListView(Sender).AlphaSort;
end;

procedure TfrmMainGui.ListViewCompare(Sender: TObject; Item1,
  Item2: TListItem; Data: Integer; var Compare: Integer);
var
  i, comp: Integer;
  numsort: Boolean;
  sortcolumnindex: Integer;
begin
  numsort := ((inventionCalc.UserSettings.Settings.SortColumn = COL_ID) or
              (inventionCalc.UserSettings.Settings.SortColumn = COL_InventionProfit) or
              (inventionCalc.UserSettings.Settings.SortColumn = COL_BuildCost) or
              (inventionCalc.UserSettings.Settings.SortColumn = COL_T1ItemProfit)
             );
  sortcolumnindex := -1;
  if (inventionCalc.UserSettings.Settings.SortColumn <> COL_NONE) then begin
    for i := 0 to 10 do begin
      if (inventionCalc.UserSettings.Settings.Columns[i] = inventionCalc.UserSettings.Settings.SortColumn) then begin
        sortcolumnindex := i;
        break;
      end;
    end;
    if (sortcolumnindex > -1) then
      comp := Sort(Item1, Item2, numsort, sortcolumnindex, inventionCalc.UserSettings.Settings.SortDirection)
    else comp := 0;
  end
  else comp := 0;

  if ((comp = 0) and (inventionCalc.UserSettings.Settings.SortColumn2 <> COL_NONE)) then begin
    numsort := ((inventionCalc.UserSettings.Settings.SortColumn2 = COL_ID) or
                (inventionCalc.UserSettings.Settings.SortColumn2 = COL_InventionProfit) or
                (inventionCalc.UserSettings.Settings.SortColumn2 = COL_BuildCost) or
                (inventionCalc.UserSettings.Settings.SortColumn2 = COL_T1ItemProfit)
               );
    sortcolumnindex := -1;
    for i := 0 to 10 do begin
      if (inventionCalc.UserSettings.Settings.Columns[i] = inventionCalc.UserSettings.Settings.SortColumn2) then begin
        sortcolumnindex := i;
        break;
      end;
    end;
    if (sortcolumnindex > -1) then
      comp := Sort(Item1, Item2, numsort, sortcolumnindex, inventionCalc.UserSettings.Settings.SortDirection2)
    else comp := 0;
  end;

  if ((comp = 0) and (inventionCalc.UserSettings.Settings.SortColumn3 <> COL_NONE)) then begin
    numsort := ((inventionCalc.UserSettings.Settings.SortColumn3 = COL_ID) or
                (inventionCalc.UserSettings.Settings.SortColumn3 = COL_InventionProfit) or
                (inventionCalc.UserSettings.Settings.SortColumn3 = COL_BuildCost) or
                (inventionCalc.UserSettings.Settings.SortColumn3 = COL_T1ItemProfit)
               );
    sortcolumnindex := -1;
    for i := 0 to 10 do begin
      if (inventionCalc.UserSettings.Settings.Columns[i] = inventionCalc.UserSettings.Settings.SortColumn3) then sortcolumnindex := i;
    end;
    if (sortcolumnindex > -1) then
      comp := Sort(Item1, Item2, numsort, sortcolumnindex, inventionCalc.UserSettings.Settings.SortDirection3)
    else comp := 0;
  end;                  
  Compare := comp;
end;

procedure TfrmMainGui.ListViewSelectItem(Sender: TObject; Item: TListItem;
  Selected: Boolean);
begin
  LoadInvention;
end;

procedure TfrmMainGui.ListViewChange(Sender: TObject; Item: TListItem;
  Change: TItemChange);
begin
//  if (Change = ctState) then
//    LoadInvention;
end;

//=========================================
//====== Invention job cost handlers ======
//=========================================

procedure TfrmMainGui.imgShowInfo1Click(Sender: TObject);
var
  frm: TfrmShowInfo;
begin
  frm := TfrmShowInfo.Create(self, inventionCalc, activeinvention.T1Item, ACT_None);
  frm.ShowModal;
  frm.Destroy;
end;

procedure TfrmMainGui.imgShowInfo2Click(Sender: TObject);
var
  frm: TfrmShowInfo;
begin
  frm := TfrmShowInfo.Create(self, inventionCalc, activeinvention.T2Item, ACT_None);
  frm.ShowModal;
  frm.Destroy;
end;

procedure TfrmMainGui.lblRequiredMaterialsClick(Sender: TObject);
var
  frm: TfrmShowInfo;
begin
  frm := TfrmShowInfo.Create(self, inventionCalc, activeinvention.T1Item, ACT_Invention);
  frm.ShowModal;
  frm.Destroy;
end;

procedure TfrmMainGui.lstT2ItemsChange(Sender: TObject; Item: TListItem;
  Change: TItemChange);
begin
  if (lstT2Items.Selected <> nil) then begin
    activeinvention.T2Item := TNewItem(lstT2Items.Selected.Data);
    UpdateInvention;
  end;
end;

procedure TfrmMainGui.cbDecryptorsChange(Sender: TObject);
begin
  if ((cbDecryptors.ItemIndex >= 0) and (cbDecryptors.Items.Objects[cbDecryptors.ItemIndex] <> nil)) then begin
    activeinvention.Decryptor := TDecryptor(cbDecryptors.Items.Objects[cbDecryptors.ItemIndex]);
  end
  else begin
    activeinvention.Decryptor := nil;
  end;
  UpdateInvention;
end;

procedure TfrmMainGui.edtMetaItemChange(Sender: TObject);
var
  price: Double;
begin
  if (TryStrToFloat(TEdit(Sender).Text, price)) then begin
    activeinvention.MetaItemCost := price;
  end
  else begin
    activeinvention.MetaItemCost := 0;
  end;
  UpdateInvention;
end;

procedure TfrmMainGui.cbMetaItemChange(Sender: TObject);
var
  level: Integer;
  s: String;
begin
  s := TComboBox(Sender).Items[TComboBox(Sender).ItemIndex];
  if (TryStrToInt(s, level)) then begin
    activeinvention.MetaItemLevel := level;
  end;
  UpdateInvention;
end;

procedure TfrmMainGui.btnCopyClick(Sender: TObject);
var
  s: String;
  i: Integer;
  askagain: Boolean;
begin
  s := IntToStr(activeinvention.BPCRuns);
  askagain := true;
  while (askagain) do begin
    if (InputQuery('Invention Calculator - Set BPC runs', 'How many runs should the BPC have?', s)) then begin
      if (TryStrToInt(s, i)) then begin
        tbCopy.Position := Min(activeinvention.T1Item.ProductionLimit, i);
        activeinvention.BPCRuns := i;
        askagain := false;
        UpdateInvention;
      end;
    end
    else askagain := false;
  end;
end;

procedure TfrmMainGui.tbCopyChange(Sender: TObject);
begin
  if (not loading) then begin
    activeinvention.BPCRuns := tbCopy.Position;
    lblItemBPCRuns.Caption := IntToStr(tbCopy.Position);
    UpdateInvention;
  end;
end;

//========================================
//====== Invention revenue handlers ======
//========================================

procedure TfrmMainGui.edtItemCostChange(Sender: TObject);
var
  price: Double;
  location: Integer;
begin
  if (not loading) then begin
    location := edtItemCost.SelStart;
    edtItemCost.Text := CorrectPrice(edtItemCost.Text);
    if (TryStrToFloat(TEdit(Sender).Text, price)) then begin
      activeinvention.T2Item.Price := price;
      UpdateInvention(false);
    end;
    edtItemCost.SelStart := location;
  end;
end;

procedure TfrmMainGui.lblBuildCostClick(Sender: TObject);
var
  frm: TfrmShowInfo;
begin
  frm := TfrmShowInfo.Create(self, inventionCalc, activeinvention.T2Item, ACT_Manufacture, activeinvention.GetOutputME, activeinvention.GetOutputRuns);
  frm.ShowModal;
  frm.Destroy;
end;

//===================================
//====== mnFile event handlers ======
//===================================

procedure TfrmMainGui.mnFileCategorizeClick(Sender: TObject);
begin
  if (organizegui = nil) then begin
    organizegui := TfrmOrganize.Create(self, inventionCalc);
    organizegui.OnClose := frmOrganizeClose;
    organizegui.Show;
  end
  else
    organizegui.BringToFront;
end;

procedure TfrmMainGui.frmOrganizeClose(Sender: TObject; var Action: TCloseAction);
begin
  organizegui := nil;
//  LoadFilter;
  LoadItems;
  ClearInvention;
end;

//===================================
//====== mnEdit event handlers ======
//===================================

procedure TfrmMainGui.mnEditItemsClick(Sender: TObject);
begin
  if (editnewitemsgui = nil) then begin
    editnewitemsgui := TfrmEditNewItems.Create(self, inventionCalc);
    editnewitemsgui.OnClose := frmEditNewItemsClose;
    editnewitemsgui.Show;
  end
  else
    editnewitemsgui.BringToFront;
end;

procedure TfrmMainGui.frmEditNewItemsClose(Sender: TObject; var Action: TCloseAction);
begin
  editnewitemsgui := nil;
  if (not isClosing) then begin
    self.LoadItems;
    self.ClearInvention;
  end;
end;

procedure TfrmMainGui.mnEditSettingsClick(Sender: TObject);
begin
  if (usersettingsgui = nil) then begin
    usersettingsgui := TfrmUserSettings.Create(self, inventionCalc, inventionCalc.UserSettings);
    usersettingsgui.OnClose := frmUserSettingsClose;
    usersettingsgui.Show;
  end
  else
    usersettingsgui.BringToFront;
end;

procedure TfrmMainGui.frmUserSettingsClose(Sender: TObject; var Action: TCloseAction);
begin
  usersettingsgui := nil;
  if (not isClosing) then begin
    self.LoadItems;
    self.ClearInvention;
  end;
end;

//====================================
//====== mnExtra event handlers ======
//====================================

// Shopping

procedure TfrmMainGui.mnExtraShoppingListClick(Sender: TObject);
begin
  if (shoppinggui = nil) then begin
    shoppinggui := TfrmShopping.Create(self, inventionCalc);
    shoppinggui.OnClose := frmShoppingClose;
    shoppinggui.Show;
  end
  else
    shoppinggui.BringToFront;
end;

procedure TfrmMainGui.frmShoppingClose(Sender: TObject; var Action: TCloseAction);
begin
  shoppinggui := nil;
end;

procedure TfrmMainGui.mnExtraShoppingAddT2Click(Sender: TObject);
var
  item: TShoppingItem;
  amountstring: String;
  amount: Integer;
  askagain: Boolean;
begin
  askagain := ((activeinvention <> nil) and (activeinvention.T2Item <> nil));
  while (askagain) do begin
    amountstring := IntToStr(activeinvention.GetOutputRuns);
    if (InputQuery('Invention Calculator - Enter amount for shopping list', 'How many times should a ' + activeinvention.T2Item.Name + ' be added to the shopping list?', amountstring)) then begin
      if (TryStrToInt(amountstring, amount)) then begin
        item := TShoppingItem.Create(activeinvention.T2Item, amount, SK_Manufacture, activeinvention.GetOutputME, inventionCalc.UserSettings.Installations.ManufactureMaterialModifier * inventionCalc.UserSettings.Skills.GetProductionEfficiencyModifier);
        inventionCalc.ShoppingList.AddItem(item);
        askagain := false;
//        mnFileShoppingClick(Sender);
      end;
    end
    else askagain := false;
  end;
end;

procedure TfrmMainGui.mnExtraShoppingAddInventionClick(Sender: TObject);
var
  item: TShoppingItem;
  amountstring: String;
  amount: Integer;
  askagain: Boolean;
begin
  askagain := ((activeinvention <> nil) and (activeinvention.T1Item <> nil));
  amountstring := '1';
  while (askagain) do begin
    if (InputQuery('Invention Calculator - Enter amount for shopping list', 'How many times do you want to invent with a ' + activeinvention.T1Item.Name + '?', amountstring)) then begin
      if (TryStrToInt(amountstring, amount)) then begin
        if (activeinvention.Decryptor <> nil) then
          item := TShoppingItem.Create(activeinvention.T1Item, amount, SK_Invention, 0, 1, activeinvention.Decryptor)
        else item := TShoppingItem.Create(activeinvention.T1Item, amount, SK_Invention);
        inventionCalc.ShoppingList.AddItem(item);
        askagain := false;
//        mnFileShoppingClick(Sender);
      end;
    end
    else askagain := false;
  end;
end;

// Invention Results

procedure TfrmMainGui.mnExtraResultsListClick(Sender: TObject);
begin
  if (resultsgui = nil) then begin
    resultsgui := TfrmResults.Create(self, inventionCalc);
    resultsgui.OnClose := frmResultsClose;
    resultsgui.Show;
  end
  else
    resultsgui.BringToFront;
end;

procedure TfrmMainGui.frmResultsClose(Sender: TObject; var Action: TCloseAction);
begin
  resultsgui := nil;
end;

procedure TfrmMainGui.mnResultsAddClick(Sender: TObject);
begin
  if (addresultsgui = nil) then begin
    if (activeinvention <> nil) then begin
      addresultsgui := TfrmAddResult.Create(self, inventionCalc, activeinvention.T2Item);
      addresultsgui.OnClose := frmAddResultsClose;
      addresultsgui.Show;
    end;
  end
  else
    addresultsgui.BringToFront;
end;

procedure TfrmMainGui.frmAddResultsClose(Sender: TObject; var Action: TCloseAction);
begin
  addresultsgui := nil;
end;

//===================================
//====== mnHelp event handlers ======
//===================================

procedure TfrmMainGui.mnHelpHelpClick(Sender: TObject);
var
  run: TFileRun;
begin
  run := TFileRun.Create(self);
  run.FileName := 'help\Index.html';
  run.Execute;
  run.Free;
end;

procedure TfrmMainGui.mnHelpAboutClick(Sender: TObject);
var
  frm: TfrmHelp;
begin
  frm := TfrmHelp.Create(self);
  frm.ShowModal;
  frm.Destroy;
end;

//====================================
//====== mnAdmin event handlers ======
//====================================

procedure TfrmMainGui.mnAdminDecryptorsClick(Sender: TObject);
var
  XMLDoc: TXmlDocument;
  i, j: Integer;
  decryptorsXML: IXMLNode;
  decryptorRace: String;
  decryptorRaceXML: IXMLNode;
  decryptorXML: IXMLNode;
  types, typenode: IXMLNode;
  decrtype: SDecryptorType;
  decr: SDecryptor;
  stream: TMemoryStream;
begin
  if (not FileExists(ExtractFileDir(Application.ExeName) + '\res\decryptors.xml')) then begin
    MessageDlg('Unable to locate \res\decryptors.xml', mtError, [mbOk], 0);
  end
  else begin
    stream := TMemoryStream.Create;
    XMLDoc := TXMLDocument.Create(self);
    XMLDoc.LoadFromFile(ExtractFileDir(Application.ExeName) + '\res\decryptors.xml');
    decryptorsXML := XMLDoc.DocumentElement;
    types := decryptorsXML.ChildNodes[0];
    i := types.ChildNodes.Count;
    stream.Write(i, SizeOf(Integer));
    for i := 0 to types.ChildNodes.Count -1 do begin
      typenode := types.ChildNodes[i];
      decrtype.Name := typenode.ChildNodes['Name'].Text;
      decrtype.ID := StrToInt(typenode.ChildNodes['ID'].Text);
      decrtype.Probability := StrToFloat(typenode.ChildNodes['Probability'].Text);
      decrtype.ME := StrToInt(typenode.ChildNodes['ME'].Text);
      decrtype.PE := StrToInt(typenode.ChildNodes['PE'].Text);
      decrtype.Runs := StrToInt(typenode.ChildNodes['Runs'].Text);
      stream.Write(decrtype, SizeOf(decrtype));
    end;

    for i := 1 to decryptorsXML.ChildNodes.Count -1 do begin // first child are the decryptor types
      { loops through the decrytors of the 4 races }
      decryptorRaceXML := decryptorsXML.ChildNodes[i];
      decryptorRace := decryptorRaceXML.Attributes['Name'];
      for j := 0 to decryptorRaceXML.ChildNodes.Count -1 do begin
        { loops through each decryptor of this race and load it }
        decryptorXML := decryptorRaceXML.ChildNodes[j];

        decr.Name := decryptorXML.ChildNodes['Name'].Text;
        decr.ID := StrToInt(decryptorXML.ChildNodes['ID'].Text);
        decr.TypeID := StrToInt(decryptorXML.ChildNodes['typeID'].Text);
        if (decryptorRace = 'Amarr') then decr.Race := Amarr
        else if (decryptorRace = 'Caldari') then decr.Race := Caldari
        else if (decryptorRace = 'Gallente') then decr.Race := Gallente
        else if (decryptorRace = 'Minmatar') then decr.Race := Minmatar;
//        decr.Probability := StrToFloat(decryptorXML.ChildNodes['probability'].Text);
//        decr.ME := StrToInt(decryptorXML.ChildNodes['me'].Text);
//        decr.PE := StrToInt(decryptorXML.ChildNodes['pe'].Text);
//        decr.Runs := StrToInt(decryptorXML.ChildNodes['runs'].Text);

        stream.Write(decr, SizeOf(SDecryptor));
      end;
    end;
    try
      stream.SaveToFile('res\decryptors');
    except
      on e: Exception do MessageDlg(e.Message, mtError, [mbOk], 0);
    end;
    stream.Free;
  end;
end;

procedure TfrmMainGui.mnAdminGenerateT1T2Click(Sender: TObject);
// generates a text file with 2 columns, the first one holds T2 item IDs, the second T1 item IDs.
// Where as expected the T1 item and t2 item are linked
// OBSOLETE with the new database dump.
var
  Fin: TextFile;
  Fout: TextFile;
  line: String;
  typeID, parentTypeID, metaGroupID: String;
  i, count: Integer;
begin
  if (not FileExists('res\dbo_invMetaTypes.sql')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\res\dbo_invMetaTypes.sql.', mtError, [mbOk], 0);
    exit;
  end;
  AssignFile(Fin, 'res\dbo_invMetaTypes.sql');
  AssignFile(Fout, 'res\T1-T2.txt');
  Reset(Fin);
  ReWrite(Fout);
  count := 0;
  while (not EOF(Fin)) do begin
    ReadLn(Fin, line);
    if (AnsiStartsText('VALUES(', line)) then begin
      line := RightStr(line, Length(line) - Pos('(', line));
      typeID := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      parentTypeID := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      metaGroupID := LeftStr(line, Pos(')', line) - 1);
      if (TryStrToInt(metaGroupID, i)) then begin
        if (i = 2) then begin
          line := typeID + #9 + parentTypeID;
          WriteLn(Fout, line);
          count := count + 1;
        end;
      end;
    end;
  end;
  CloseFile(Fin);
  CloseFile(Fout);
  ShowMessage('Written ' + IntToStr(count) + ' lines to res\T1-T2.txt');
end;

procedure TfrmMainGui.mnAdminImportClick(Sender: TObject);
// Imports everything needed from the database files
// Note that the constand 29437 is the highest ID in invTypes.sql. Adjust for new imports
var
  Fin: TextFile;
  line, line2: String;
  typeID,typeName,description,volume,portionSize,raceID,marketGroupID: String; // read from items.csv
  blueprintTypeID, productTypeID, productionTime, productivityModifier, researchTechTime, researchCopyTime, wasteFactor, chanceOfReverseEngineering, maxProductionLimit: String; // read from blueprints.csv
  {typeID,}activity,requiredTypeID,quantity,damagePerJob: String; // read from materials.csv
  qty: Integer;
  dam: Double;
  i, j, len, cT1Item, cT2Item, cOther: Integer;
  d: Double;
  a: array [0..29511] of array of Integer;
  b: array [0..29511] of Boolean;
  blueprints: array [0..29511] of TNewItem;
  itemamount: TItemAmount;
  item, linkeditem: TNewItem;
  act: EActivity;
  userresult: Integer;
  reloaduseritems: Boolean;
begin
// first prepare the program to load new items
  userresult := MessageDlg('Importing items will clear your shoping list and invention results. Do you want to reload your item-specific information, such as prices and category after the import?', mtWarning, [mbYes, mbNo, mbCancel], 0);
  if (userresult = mrCancel) then exit;
  reloaduseritems := (userresult = mrYes);
  inventionCalc.ShoppingList.Clear;
  inventionCalc.Results.Clear;
  inventionCalc.ClearItems;

// check if needed files exist...
  if (not FileExists('Database export\T1-T2.rpt')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\Database export\T1-T2.rpt.', mtError, [mbOk], 0);
    exit;
  end;
  if (not FileExists('Database export\items.rpt')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\Database export\items.rpt.', mtError, [mbOk], 0);
    exit;
  end;
  if (not FileExists('Database export\blueprints.rpt')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\Database export\blueprints.rpt.', mtError, [mbOk], 0);
    exit;
  end;
  if (not FileExists('Database export\materials.rpt')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + '\Database export\materials.rpt.', mtError, [mbOk], 0);
    exit;
  end;
// check if needed files exist... done

// load T1 - T2 items, so only inventable items can be retrieved...
  AssignFile(Fin, 'Database export\T1-T2.rpt');
  Reset(Fin);
  for i := 0 to 29511 do begin
    SetLength(a[i], 0);
    b[i] := false;
  end;
  while (not EOF(Fin)) do begin
    ReadLn(Fin, line);
    // leftstr has parentID (T2 item)
    line2 := LeftStr(line, Pos(global_delimiter, line) -1);
    if (TryStrToInt(line2, j)) then begin
      b[j] := true;
    end;
    // rightstr has typeID (T1 item)
    line2 := RightStr(line, Length(line) - Pos(global_delimiter, line));
    if (TryStrToInt(line2, i)) then begin
      len := Length(a[i]);
      SetLength(a[i], len +1);
      a[i][len] := j;
    end;
  end;
  CloseFile(Fin);
// load T1 - T2 items, so only inventable items can be retrieved... done

// load all items by:...
  AssignFile(Fin, 'Database export\items.rpt');
  Reset(Fin);
  cT1Item := 0;
  cT2Item := 0;
  cOther := 0;
// first reading the line...
  while (not EOF(Fin)) do begin
    ReadLn(Fin, line);
    if (line = '') then break;
    typeID := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));

    while (Pos(global_delimiter, line) = 0) do begin
      ReadLn(Fin, line2);
      line := line + #10#13 + line2;
    end;
    typeName := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));

    while (Pos(global_delimiter, line) = 0) do begin
      ReadLn(Fin, line2);
      line := line + #10#13 + line2;
    end;
    description := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    volume := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    portionSize := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    raceID := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    marketGroupID := line;
// first reading the line... done

// then determining if and how it should be added...
    if (TryStrToInt(typeID, i)) then begin
      if (Length(a[i]) > 0) then begin
        // T1 item that has one or more T2 versions
        item := TNewItem.Create(typeName, i, IT_T1Item);
//        volume := StringReplace(volume, '.', ',', [rfReplaceAll]); replacement no longer necessary as SQLServer puts it in the right locale
        if (TryStrToFloat(volume, d)) then item.Volume := d;
        if (TryStrToInt(portionSize, j)) then item.PortionSize := j;
        item.Race := StringToERace(raceID);
        for j := 0 to Length(a[i]) -1 do begin
          item.AddUsedIn(a[i][j], inventionCalc.GetItem(a[i][j]), 1);
        end;
        inventionCalc.AddItem(item);
        cT1Item := cT1Item + 1;
      end
      else if (b[i]) then begin
        // T2 that has a T1 item
        item := TNewItem.Create(typeName, i, IT_T2Item);
//        volume := StringReplace(volume, '.', ',', [rfReplaceAll]); replacement no longer necessary as SQLServer puts it in the right locale
        if (TryStrToFloat(volume, d)) then item.Volume := d;
        if (TryStrToInt(portionSize, j)) then item.PortionSize := j;
        item.Race := StringToERace(raceID);
        inventionCalc.AddItem(item);
        cT2Item := cT2Item + 1;
      end
      else if (TryStrToInt(marketGroupID, j)) then begin
        if (MarketGroupIDToEItemType(j) <> IT_Unknown) then begin
          // mineral
          item := TNewItem.Create(typeName, i, MarketGroupIDToEItemType(j));
//          volume := StringReplace(volume, '.', ',', [rfReplaceAll]); replacement no longer necessary as SQLServer puts it in the right locale
          if (TryStrToFloat(volume, d)) then item.Volume := d;
          item.Race := StringToERace(raceID);
          inventionCalc.AddItem(item);
          cOther := cOther + 1;
        end;
      end;
    end;
// then determining if and how it should be added... done
  end;
  CloseFile(Fin);
// load all items by:... done

// check all items that were just added, and link the item correctly to other items...
  for i := 0 to inventionCalc.GetNewItemCount -1 do begin
    item := TNewItem(inventionCalc.GetItemByIndex(i));
    if (item <> nil) then begin
      { loops below use downto because items are deleted and added while the array they belong to is inspected.
        This works because adding an item (to these arrays) always adds the item at the end. }
      for j := item.UsedInCount -1 downto 0 do begin
        itemamount := item.GetUsedIn(j);
        linkeditem := TNewItem(inventionCalc.GetItem(itemamount.ID));
        item.SetLinkedItem(itemamount, linkeditem);
      end;
      for j := item.RequirementsCount -1 downto 0 do begin
        itemamount := item.GetRequirement(j);
        linkeditem := TNewItem(inventionCalc.GetItem(itemamount.ID));
        item.SetLinkedItem(itemamount, linkeditem);
      end;
    end;
  end;
// check all items that were just added, and link the item correctly to other items... done

// let the user know we're still doing stuff
  ShowMessage('Loaded the following items:' + #10#13#10#13 +
              IntToStr(cT1Item) + ' T1 items ' + #10#13 +
              IntToStr(cT2Item) + ' T2 items ' + #10#13 +
              IntToStr(cOther) + ' other items, such as minerals, datacores, components, etc. ' + #10#13#10#13 +
              'For a total of ' + IntToStr(cT1Item+cT2Item+cOther) + ' items. For Trinity 1.0 this number should be 1188.' + #10#13#10#13 +
              'Looking up blueprint information... (this will take a while)');

// load blueprint information, such as manufacturing time...
  AssignFile(Fin, 'Database export\blueprints.rpt');
  Reset(Fin);
  while (not EOF(Fin)) do begin
    ReadLn(Fin, line);
    if (line = '') then break;
    blueprintTypeID := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    productTypeID := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    productionTime := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    researchCopyTime := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    researchTechTime := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    productivityModifier := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    wasteFactor := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    chanceOfReverseEngineering := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    maxProductionLimit := line;
    if (TryStrToInt(productTypeID, i)) then begin
      item := inventionCalc.GetItem(i);
      // save item that corresponds with this blueprint, so when looking at TL2MaterialsForTypeWithActivity.sql, stuff can be added to the right item
      if (TryStrToInt(blueprintTypeID, j)) then blueprints[j] := item;
      if (item <> nil) then begin
        if (TryStrToInt(productionTime, i)) then item.BuildTime := i;
        if (TryStrToInt(productivityModifier, i)) then item.ProductivityModifier := i;
        if (TryStrToInt(researchTechTime, i)) then item.InventionTime := i;
        if (TryStrToInt(researchCopyTime, i)) then item.CopyTime := i;
        if (TryStrToInt(wasteFactor, i)) then item.WasteFactor := i;
//        chanceOfReverseEngineering := StringReplace(chanceOfReverseEngineering, '.', global_ds, [rfReplaceAll]); replacement no longer necessary as SQLServer puts it in the right locale
        if (TryStrToFloat(chanceOfReverseEngineering, d)) then item.BaseChance := d;
        if (TryStrToInt(maxProductionLimit, i)) then item.ProductionLimit := i;
      end;
    end;
  end;
  CloseFile(Fin);
// load blueprint information, such as manufacturing time... done

// load linked blueprint information, such as material requirements for building items, datacores for invention...
  AssignFile(Fin, 'Database export\materials.rpt');
  Reset(Fin);
  while (not EOF(Fin)) do begin
    ReadLn(Fin, line);
    if (line = '') then break;
    typeID := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    activity := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    requiredTypeID := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    quantity := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    damagePerJob := line;
//    damagePerJob := StringReplace(damagePerJob, '.', global_ds, [rfReplaceAll]);
    if ((TryStrToInt(typeID, i)) and (TryStrToInt(requiredTypeID, j)) and (TryStrToInt(quantity, qty)) and (TryStrToFloat(damagePerJob, dam))) then begin
      item := blueprints[i];
      if (item <> nil) then begin
        act := StringToEActivity(activity);
        if (act <> ACT_None) then begin
          linkeditem := inventionCalc.GetItem(j);
          if (qty < 0) then qty := 0;
          if (linkeditem = nil) then begin
 //             MessageDlg('Unable to add item with ID ' + IntToStr(j) + ' to ' + item.Name + '.', mtError, [mbOk], 0);
          end
          else if (qty > 0) then begin
            if (linkeditem.ItemType = IT_DataInterface) then begin
              // also set invention type for the item
              if (AnsiContainsText(linkeditem.Name, 'Ship')) then item.InventionType := Ship
              else if (AnsiContainsText(linkeditem.Name, 'Tuner')) then item.InventionType := Rig
              else item.InventionType := Normal;
              // as well as the invention race
              if (AnsiContainsText(linkeditem.Name, 'Occult')) then item.InventionRace := Amarr
              else if (AnsiContainsText(linkeditem.Name, 'Esoteric')) then item.InventionRace := Caldari
              else if (AnsiContainsText(linkeditem.Name, 'Incognito')) then item.InventionRace := Gallente
              else if (AnsiContainsText(linkeditem.Name, 'Cryptic')) then item.InventionRace := Minmatar
              else item.InventionRace := RACE_None;
              // and of course add the item as (invention) requirement
              item.AddRequirement(linkeditem.ID, linkeditem, act, qty, false, 0); // manually set the damage to 0 for interfaces (as they're not consumed in invention, contrary to what the database dump says
            end
            else
              // if it is not an interface, simply add the item
              item.AddRequirement(linkeditem.ID, linkeditem, act, qty, false, dam);
          end;
        end;
      end;
    end;
  end;
  CloseFile(Fin);
// load linked blueprint information, such as material requirements for building items, datacores for invention... done

// check all (T1) items that were added, and set their respective T2 items' type to the same...
// needed because waste calculation is different for ships compared to other items
  for i := 0 to inventionCalc.GetNewItemCount -1 do begin
    item := inventionCalc.GetItemByIndex(i);
    if (item <> nil) then begin
      // Set item itself to the right build type
      if (item.InventionType = Ship) then
        item.BuildType := Ship
      else if (item.BuildType <> Ship) then
        item.BuildType := Normal;

      // Then also set T2 ships to the right build type, by checking their T1 hull for invention results
      // Has to be done this way, because T2 items do not have invention types
      if ((item.ItemType = IT_T1Item) and (item.BuildType = Ship)) then begin
        for j := 0 to item.UsedInCount -1 do begin
          item.GetUsedIn(j).Item.BuildType := Ship;
        end;
      end;
    end;
  end;
// check all (T1) items that were added, and set their respective T2 items' type to the same... done

// reload user settings for items (such as category, subcategory and group)
  inventionCalc.LinkItems(reloaduseritems);

  ShowMessage('Done!');
end;

procedure TfrmMainGui.mnAdminImportInstallationsClick(Sender: TObject);
var
  Fin: TextFile;
  line: String;
  loc: SLocation;
  assemblyLineTypeID, assemblyLineTypeName, baseTimeMultiplier, baseMaterialMultiplier, activityID: String;
  materialmodifier, timemodifier: Double;
  id, count: Integer;
begin
  if (not FileExists('Database export\installations.rpt')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + 'Database export\installations.rpt.', mtError, [mbOk], 0);
    exit;
  end;
  AssignFile(Fin, 'Database export\installations.rpt');
  Reset(Fin);
  count := 0;
  inventionCalc.Locations.Clear;
  while (not EOF(Fin)) do begin
    ReadLn(Fin, line);
    if (line = '') then break;
    assemblyLineTypeID := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    assemblyLineTypeName := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    baseTimeMultiplier := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    baseMaterialMultiplier := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    activityID := line;

//    assemblyLineTypeName := RightStr(assemblyLineTypeName, Length(assemblyLineTypeName) -1);
//    assemblyLineTypeName := LeftStr(assemblyLineTypeName, Length(assemblyLineTypeName) -1);
//    baseTimeMultiplier := StringReplace(baseTimeMultiplier, '.', global_ds, [rfReplaceAll]);
//    baseMaterialMultiplier := StringReplace(baseMaterialMultiplier, '.', global_ds, [rfReplaceAll]);
    if ((TryStrToInt(assemblyLineTypeID, id)) and (TryStrToFloat(baseMaterialMultiplier, materialmodifier)) and (TryStrToFloat(baseTimeMultiplier, timemodifier))) then begin
      // now filter out the useless locations such as test values, booster production and the ORE ship
      if ((StringToEActivity(activityID) <> ACT_None) and (id <> 2) and (id <> 13) and (id <> 36) and (id <> 37) and (id <> 145) and (id <> 150) and (id <> 2)) then begin
        loc.Name := assemblyLineTypeName;
        loc.ID := id;
        loc.MaterialModifier := materialmodifier;
        loc.TimeModifier := timemodifier;
        loc.Activity := StringToEActivity(activityID);
        inventionCalc.Locations.Add(TLocation.Create(loc));
        count := count + 1;
      end;
    end;
  end;
  MessageDlg('Loaded ' + IntToStr(count) + ' locations.', mtInformation, [mbOk], 0);
end;

procedure TfrmMainGui.mnAdminImportRegionsClick(Sender: TObject);
var
  Fin: TextFile;
  line: String;
  reg: SRegion;
  regionID, regionName,factionID: String;
  id, count: Integer;
begin
  if (not FileExists('Database export\regions.rpt')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + 'Database export\regions.rpt.', mtError, [mbOk], 0);
    exit;
  end;
  AssignFile(Fin, 'Database export\regions.rpt');
  Reset(Fin);
  count := 0;
  inventionCalc.Regions.Clear;
  while (not EOF(Fin)) do begin
    ReadLn(Fin, line);
    if (line = '') then break;
    regionID := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    regionName := LeftStr(line, Pos(global_delimiter, line) - 1);
    line := RightStr(line, Length(line) - Pos(global_delimiter, line));
    factionID := line;

    if (TryStrToInt(regionID, id)) then begin
      reg.Name := regionName;
      reg.ID := id;
      reg.Faction := StringToEFaction(factionID);
      if (reg.Faction <> FAC_Jove) then inventionCalc.Regions.Add(TRegion.Create(reg));
      count := count + 1;
    end;
  end;
  MessageDlg('Loaded ' + IntToStr(count) + ' regions.', mtInformation, [mbOk], 0);
end;

procedure TfrmMainGui.mnAdminListMarketgroupsClick(Sender: TObject);
// As the name suggests, this lists all market groups that have items in them.
// Note that this can have unexpected results, such as loads of market groups called 'Amarr', or 'Caldari'.
// This is because (in this case) ships are grouped per race for each shiptype. As there are no ships directly
// under f.e. "Exhumers", the market group "Exhumers" will not appear in the output file
var
  Fin: TextFile;
  Fout: TextFile;
  line: String;
  typeID,groupID,typeName,description,graphicID,radius,mass,volume,capacity,portionSize,raceID,basePrice,published,marketGroupID,chanceOfDuplicating: String;
  i, count: Integer;
  a: array [0..1105] of Boolean;
begin
  if (not FileExists('res\dbo_invTypes.sql')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + 'res\dbo_invTypes.sql.', mtError, [mbOk], 0);
    exit;
  end;
  AssignFile(Fin, 'res\dbo_invTypes.sql');
  Reset(Fin);
  count := 0;
  for i := 0 to 1105 do
    a[i] := false;
  while (not EOF(Fin)) do begin
    ReadLn(Fin, line);
    if (AnsiStartsText('VALUES(', line)) then begin
      line := RightStr(line, Length(line) - Pos('(', line));
      typeID := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      groupID := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));

      typeName := LeftStr(line, Pos(#39#44, line) - 1);
      typeName := RightStr(typeName, Length(typeName) -1);
      line := RightStr(line, Length(line) - Pos(#39#44, line) -1);

      description := LeftStr(line, Pos(#39#44, line) - 1);
      description := RightStr(description, Length(description) -1);
      line := RightStr(line, Length(line) - Pos(#39#44, line) -1);

      graphicID := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      radius := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      mass := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      volume := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      capacity := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      portionSize := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      raceID := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      basePrice := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      published := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      marketGroupID := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      chanceOfDuplicating := LeftStr(line, Pos(')', line) - 1);
      if (published = 'true') then begin
        if (TryStrToInt(marketGroupID, i)) then begin
          a[i] := true;
        end;
      end;
    end;
  end;
  AssignFile(Fout, 'res\MarketgroupsWithItems.txt');
  ReWrite(Fout);
  for i := 0 to 1105 do begin
    if (a[i]) then begin
      WriteLn(Fout, IntToStr(i));
      count := count + 1;
    end;
  end;
  CloseFile(Fin);
  CloseFile(Fout);
  ShowMessage('Written ' + IntToStr(count) + ' lines to res\MarketgroupsWithItems.txt');
end;

procedure TfrmMainGui.mnAdminListItemsMarketGroupClick(Sender: TObject);
// Lists each item with their market group
var
  Fin: TextFile;
  Fout: TextFile;
  line: String;
  marketGroupID,parentGroupID,marketGroupName,description,graphicID,hasTypes: String;
  i, p, count: Integer;
  a: array [0..1105] of Integer;
  names: array [0..1105] of String;
begin
  if (not FileExists('res\dbo_invMarketGroups.sql')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + 'res\dbo_invMarketGroups.sql.', mtError, [mbOk], 0);
    exit;
  end;
  if (not FileExists('res\MarketgroupsWithItems.txt')) then begin
    MessageDlg('Unable to locate the file ' + ExtractFileDir(Application.ExeName) + 'res\MarketgroupsWithItems.txt.', mtError, [mbOk], 0);
    exit;
  end;
  AssignFile(Fin, 'res\dbo_invMarketGroups.sql');
  AssignFile(Fout, 'res\MarketGroups.txt');
  Reset(Fin);
  ReWrite(Fout);
  count := 0;
  for i := 0 to 1105 do
    a[i] := -1;
  names[0] := 'null';
  for i := i to 1105 do
    names[i] := 'N/A';
  while (not EOF(Fin)) do begin
    ReadLn(Fin, line);
    if (AnsiStartsText('VALUES(', line)) then begin
      line := RightStr(line, Length(line) - Pos('(', line));
      marketGroupID := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      parentGroupID := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));

      marketGroupName := LeftStr(line, Pos(#39#44, line) - 1);
      marketGroupName := RightStr(marketGroupName, Length(marketGroupName) -1);
      line := RightStr(line, Length(line) - Pos(#39#44, line) -1);

      description := LeftStr(line, Pos(#39#44, line) - 1);
      description := RightStr(description, Length(description) -1);
      line := RightStr(line, Length(line) - Pos(#39#44, line) -1);

      graphicID := LeftStr(line, Pos(',', line) - 1);
      line := RightStr(line, Length(line) - Pos(',', line));
      hasTypes := LeftStr(line, Pos(')', line) - 1);
      if (TryStrToInt(marketGroupID, i)) then begin
        if (TryStrToInt(parentGroupID, p)) then begin
          a[i] := p;
        end
        else if (parentGroupID = 'null') then begin
          a[i] := 0;
        end;
        names[i] := marketGroupName;
      end;
    end;
  end;
  CloseFile(Fin);
  AssignFile(Fin, 'res\MarketgroupsWithItems.txt');
  Reset(Fin);
  while (not (EOF(Fin))) do begin
    ReadLn(Fin, line);
    if (TryStrToInt(line, i)) then begin
      line := IntToStr(i) + #9 + names[i] + ' <- ';
      p := a[i];
      while (p > 0) do begin
        line := line + names[p] + ' <- ';
        p := a[p];
      end;
      WriteLn(Fout, LeftStr(line, Length(line) -4));
      count := count + 1;
    end;
  end;
  CloseFile(Fin);
  CloseFile(Fout);
  ShowMessage('Written ' + IntToStr(count) + ' lines to res\MarketGroups.txt');
end;

procedure TfrmMainGui.mnAdminExportClick(Sender: TObject);
// Export function. Shouldn't be an admin function, but part of some nice export form 
var
  F: TextFile;
  i: Integer;
  item: TNewItem;
  inv: TInvention;
  name, pricer, runs: String;
  profit, profitpercent: Double;
begin
  profit := 0;
  profitpercent := 1.35;

  AssignFile(F, 'ExportedItems.txt');
  ReWrite(F);
  WriteLn(F, 'Amount  Item Name' + StringOfChar(' ', 29) + 'Price');
  WriteLn(F, '------------------------------------------------------------------');
  for i := 0 to inventionCalc.GetT1ItemCount -1 do begin
    item := inventionCalc.GetT1ItemByIndex(i);
    inv := TInvention.Create(item, inventionCalc);
    inv.GetSuccess; // needed to calculate the success, which will be stored inside the TInvention object

//    if (item.T2Item1 <> nil) then begin
//      runs := FloatToStr(inv.GetT2ItemOutputRuns);
//      runs := ' ' + StringOfChar(' ', 2 -  Length(runs)) + runs;
//      name := StringOfChar(' ', 5) + item.T2Item1.Name + ' ' + StringOfChar('.', 36 - Length(item.T2Item1.Name));
//      pricer := FloatToStrF(RoundTo(((inv.GetT2ItemOutputRuns * (inv.GetT2ItemBuildCost + profit) + inv.GetSuccessCost) * profitpercent), 5), ffNumber, 11, 2);
//      pricer := StringOfChar(' ', 14 - Length(pricer)) + pricer;

//      WriteLn(F, runs + name + pricer);
//    end;
//    if (item.T2Item2 <> nil) then begin
//      inv.T2Item := 2;
//      runs := FloatToStr(inv.GetT2ItemOutputRuns);
//      runs := ' ' + StringOfChar(' ', 2 -  Length(runs)) + runs;
//      name := StringOfChar(' ', 5) + item.T2Item2.Name + ' ' + StringOfChar('.', 36 - Length(item.T2Item2.Name));
//      pricer := FloatToStrF(RoundTo(((inv.GetT2ItemOutputRuns * (inv.GetT2ItemBuildCost + profit) + inv.GetSuccessCost) * profitpercent), 5), ffNumber, 11, 2);
//      pricer := StringOfChar(' ', 14 - Length(pricer)) + pricer;

//      WriteLn(F, runs + name + pricer);
//    end;
  end;
  CloseFile(F);
end;

procedure TfrmMainGui.mnAdminCreateCategoryListClick(Sender: TObject);
// creates categories, subcategories and groups.txt, which lists all items in their category, subcategory and group
// not very efficient
var
  Fout: TextFile;
  i, j, k, l: Integer;
  catcount, subcatcount, grpcount, itemcount, unknowncount: Integer;
  cat: TCategory;
  subcat: TSubCategory;
  grp: TGroup;
  item: TNewITem;
begin
  AssignFile(Fout, 'res\categories, subcategories and groups.txt');
  ReWrite(Fout);
  catcount := 0;
  subcatcount := 0;
  grpcount := 0;
  itemcount := 0;
  unknowncount := 0;
  WriteLn(Fout, 'Category');
  WriteLn(Fout, #9'Subcategory');
  WriteLn(Fout, #9#9'Group');
  WriteLn(Fout, #9#9#9'Items');
  WriteLn(Fout, '======================================================================');
  for i := 0 to inventionCalc.CategoryCount -1 do begin
    cat := inventionCalc.GetCategory(i);
    catcount := catcount +1;
    WriteLn(Fout, cat.Name);
    if (cat.SubcategoryCount > 0) then begin
      for j := 0 to cat.SubcategoryCount -1 do begin
        subcat := cat.GetSubcategory(j);
        subcatcount := subcatcount +1;
        WriteLn(Fout, #9 + subcat.Name);
        if (subcat.GroupCount > 0) then begin
          for k := 0 to subcat.GroupCount -1 do begin
            grp := subcat.GetGroup(k);
            grpcount := grpcount +1;
            WriteLn(Fout, #9#9 + grp.Name);
            for l := 0 to inventionCalc.GetT1ItemCount -1 do begin
              item := inventionCalc.GetT1ItemByIndex(l);
              if (item.Group = grp) then begin
                WriteLn(Fout, #9#9#9 + item.Name);
                itemcount := itemcount +1;
              end;
            end;
          end;
        end
        else begin
          for l := 0 to inventionCalc.GetT1ItemCount -1 do begin
            item := inventionCalc.GetT1ItemByIndex(l);
            if (item.Subcategory = subcat) then begin
              WriteLn(Fout, #9#9#9 + item.Name);
              itemcount := itemcount +1;
            end;
          end;
        end;
      end;
    end
    else begin
      for l := 0 to inventionCalc.GetT1ItemCount -1 do begin
        item := inventionCalc.GetT1ItemByIndex(l);
        if (item.Category = cat) then begin
          WriteLn(Fout, #9#9#9 + item.Name);
          itemcount := itemcount +1;
        end;
      end;
    end;
  end;
  WriteLn(Fout, 'Unassigned items');
  for l := 0 to inventionCalc.GetT1ItemCount -1 do begin
    item := inventionCalc.GetT1ItemByIndex(l);
    if (item.Category = nil) then begin
      WriteLn(Fout, #9 + item.Name);
      unknowncount := unknowncount +1;
    end;
  end;
  CloseFile(Fout);
  ShowMessage('Written the following to res\categories, subcategories and groups.txt:' + #10#13#10#13 +
              IntToStr(catcount) + ' categories,' + #10#13 +
              IntToStr(subcatcount) + ' subcategories,' + #10#13 +
              IntToStr(grpcount) + ' groups,' + #10#13 +
              IntToStr(itemcount) + ' items,' + #10#13 +
              IntToStr(unknowncount) + ' unassigned items.');
end;



procedure TfrmMainGui.AnatolixExportText1Click(Sender: TObject);
begin
  inventionCalc.SaveNewItemsText;
end;

end.

